/* The file is saved in UTF-8 codepage.
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */
package cz.pecinovsky.english.lootp.utility;



/*******************************************************************************
 *<pre>
 * Předchozí:   Není - toto_je_nově_definovaná_třída
 *              Ttt v projektu Ppp
 * Následující: Prozatím není
 *
 * Projekt  Ppp
 *   + Přidáno
 *   - Odebráno
 *   ~ Změněno
 *</pre>
 */


////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ End of ignored starting test - place for imports +++++

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;


/*******************************************************************************
 * Instance třídy {@code Clock} představují ...
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Clock
{
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** Implicitní velikost periody hodin udávaná v milisekundách. */
    private static final int IMPLICITNÍ_PERIODA = 50; //=> f=20 Hz

    /** Jediná instance hodin, kterou budou všichni sdílet. */
    private static final Clock clock = new Clock();



//== VARIABLE CLASS ATTRIBUTES =================================================
//== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ========
//== CONSTANT INSTANCE ATTRIBUTES ==============================================

    /** Přihlášení posluchači hodin, kteří reagují na jejich tiky. */
    private final List<ITimed> listeners = new ArrayList<ITimed>();

    /** Časovač, který řídí tikot hodin. */
    private Timer timer = new Timer("Časovač hodin");



//== VARIABLE INSTANCE ATTRIBUTES ==============================================

    /** Perioda tikotu hodin. */
    private int period = IMPLICITNÍ_PERIODA;

    /** Indikátor běhu hodin. */
    private boolean running = false;

    /** Úloha, která ve spolupráci s časovačem realizuje tikot hodin. */
    private Task task = null;

    /** Úvodní akce prováděná před vlastní sérií akcí reagujících na tick -
     *  např. zastavení překreslování plátna. */
    private ITimed header = null;


    /** Závěrečná akce prováděná po vlastní sérií akcí reagujících na tick -
     *  např. opětně spuštění překreslování plátna. */
    private ITimed footer = null;



//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

//##############################################################################
//== CONSTUCTORS AND FACTORY METHODS ===========================================

    /***************************************************************************
     * Vrátí odkaz na hodiny.
     *
     * @return Odkaz na hodiny
     */
    public static Clock getInstance()
    {
        return clock;
    }


    /***************************************************************************
     * Hodinám stačí prázdný bezparametrický konstruktor.
     */
    private Clock()
    {
    }



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================

    /***************************************************************************
     * Vrátí aktuálně natavenou periodu hodin.
     *
     * @return Aktuálně nastavená perioda
     */
    public synchronized int getPeriod()
    {
        return this.period;
    }


    /***************************************************************************
     * Nastaví novou periodu tikotu hodin.
     *
     * @param miliseconds Nastavovaná perioda
     */
    public synchronized void setPeriod(int miliseconds)
    {
        if (running) {
            stop();
            this.period = miliseconds;
            start();
        } else {
            this.period = miliseconds;
        }
    }


    /***************************************************************************
     * Vrátí aktuálně natavenou frekvenci tikotu hodin.
     *
     * @return Aktuálně nastavená frekvenci
     */
    public double getFrekvence()
    {
        return 1000.0 / getPeriod();
    }


    /***************************************************************************
     * Nastaví novou frekvenci tikotu hodin. Frekvence se ale nastavuje tak,
     * že se nastaví nejbližší hodnota potřebné periody.
     *
     * @param hertz Nastavovaná frekvence
     */
    public void setFrekvence(double hertz)
    {
        setPeriod((int)(1000.0 / hertz  + 0.5));
    }


    /***************************************************************************
     * Nastaví úvodní akci prováděnou před vlastní sérií akcí
     * reagujících na tick.
     *
     * @param header Objekt realizující úvodní akci
     */
    public synchronized void setHeader(ITimed header)
    {
        this.header = header;
    }


    /***************************************************************************
     * Nastaví závěrečnou akci prováděnou po vlastní sérií akcí
     * reagujících na tick.
     *
     * @param footer Objekt realizující závěrečnou akci
     */
    public synchronized void setFooter(ITimed footer)
    {
        this.footer = footer;
    }



//== OTHER NON-PRIVATE INSTANCE METHODS ========================================

    /***************************************************************************
     * Zaregistruje zadaného posluchače a bude mu posílat zprávy
     * o jednotlivých ticích hodin.
     *
     * @param listener Registrovaný posluchač
     */
    public void addListener(ITimed listener)
    {
        if (! listeners.contains(listener)) {
            synchronized(this) {
                listeners.add(listener);
            }
        }
    }


    /***************************************************************************
     * Odregistruje zadaného posluchače a přestane mu posílat zprávy
     * o jednotlivých ticích hodin.
     *
     * @param listener Registrovaný posluchač
     */
    public void removeListener(ITimed listener)
    {
        if (listeners.contains(listener)) {
            synchronized (this) {
                listeners.remove(listener);
//                //Otázka, zda pozastavovat hodiny, když už není komu tikat
//                if (listeners.size() == 0) {
//                    stop();
//                }
            }
        }
    }


    /***************************************************************************
     * Spustí hodiny se zadanou periodou.
     */
    public synchronized void start()
    {
        if (running) { return; }

        task = new Task();
        timer.scheduleAtFixedRate(task, 0, period);
        this.running = true;
    }


    /***************************************************************************
     * Zastaví hodiny, takže přestanou tikat.
     */
    public synchronized void stop()
    {
        task.cancel();
        this.running = false;
    }



//== PRIVATE AND AUXILIARY CLASS METHODS =======================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================
//== EMBEDDED TYPES AND INNER CLASSES ==========================================

    /***************************************************************************
     * Instance třídy {@code Úloha} jsou úlohy spouštěné časovačem
     * v pravidelných intervalech a jejich úkolem je oznámit všem
     * registrovaným posluchačím, že hodiny tikly.
     */
    private class Task extends TimerTask
    {
        @Override
        public void run()
        {
            synchronized(clock) {
                if (header != null) { header.tick(); }
                for (ITimed posluchač : listeners) {
                    posluchač.tick();
                }
                if (footer != null) { footer.tick(); }
            }
        }
    }



//== THE TESTS =================================================================
//
//    private static class Testovací implements ITimed {
//        static int počet  = 0;
//        final  int pořadí = ++počet;
//
//        long minule = System.currentTimeMillis();
//        int  tick    = 0;
//
//        @Override
//        public void tick()
//        {
//            long nyní = System.currentTimeMillis();
//            long doba = nyní - minule;
//            System.out.printf("T%1d - %3d - %3d.%03d\n",
//                              pořadí, ++tick, doba/1000, doba%1000);
//            minule = nyní;
//        }
//
//    }
//
//    /***************************************************************************
//     * Test method.
//     */
//    public static void test()
//    {
//        Testovací t = new Testovací();
//        hodiny.setFrekvence(1.0);
//        hodiny.addListener(t);
//        hodiny.start();
//        System.out.println("Budu čekat 5 sekund - frekvence 1");
//        IO.čekej(5000);
//        System.out.println("PO 5 sekundách - přidávám druhý objekt");
//        hodiny.addListener(new Testovací());
//        IO.čekej(3000);
//        System.out.println("Po dalších 3 sekundách - nyní frekvence 3");
//        hodiny.setFrekvence(3);
//        IO.čekej(3000);
//        System.out.println("Po dalších 3 sekundách - odebírám první objekt");
//        hodiny.removeListener(t);
//        IO.čekej(3000);
//        System.out.println("Po dalších 3 sekundách");
//        hodiny.stop();
//    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
//    public static void main(String[] args)  {  test();  }
}
